# Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

# This file contains a set of utilities functions used by other Python-based
# scripts.

import commands
import os
import platform
import Queue
import re
import StringIO
import subprocess
import sys
import threading
import time


# Try to guess the host operating system.
def GuessOS():
  id = platform.system()
  if id == "Linux":
    return "linux"
  elif id == "Darwin":
    return "macos"
  elif id == "Windows" or id == "Microsoft":
    # On Windows Vista platform.system() can return "Microsoft" with some
    # versions of Python, see http://bugs.python.org/issue1082 for details.
    return "win32"
  elif id == 'FreeBSD':
    return 'freebsd'
  elif id == 'OpenBSD':
    return 'openbsd'
  elif id == 'SunOS':
    return 'solaris'
  else:
    return None


# Try to guess the host architecture.
def GuessArchitecture():
  id = platform.machine()
  if id.startswith('arm'):
    return 'arm'
  elif (not id) or (not re.match('(x|i[3-6])86', id) is None):
    return 'ia32'
  elif id == 'i86pc':
    return 'ia32'
  else:
    return None


# Try to guess the number of cpus on this machine.
def GuessCpus():
  if os.path.exists("/proc/cpuinfo"):
    return int(commands.getoutput("GREP_OPTIONS= grep -E '^processor' /proc/cpuinfo | wc -l"))
  if os.path.exists("/usr/bin/hostinfo"):
    return int(commands.getoutput('/usr/bin/hostinfo | GREP_OPTIONS= grep "processors are logically available." | awk "{ print \$1 }"'))
  win_cpu_count = os.getenv("NUMBER_OF_PROCESSORS")
  if win_cpu_count:
    return int(win_cpu_count)
  return int(os.getenv("DART_NUMBER_OF_CORES", 2))


# Returns true if we're running under Windows.
def IsWindows():
  return GuessOS() == 'win32'


# Reads a text file into an array of strings - one for each
# line. Strips comments in the process.
def ReadLinesFrom(name):
  result = []
  for line in open(name):
    if '#' in line:
      line = line[:line.find('#')]
    line = line.strip()
    if len(line) == 0:
      continue
    result.append(line)
  return result

# Filters out all arguments until the next '--' argument
# occurs.
def ListArgCallback(option, opt_str, value, parser):
   if value is None:
     value = []

   for arg in parser.rargs:
     if arg[:2].startswith('--'):
       break
     value.append(arg)

   del parser.rargs[:len(value)]
   setattr(parser.values, option.dest, value)


# Filters out all argument until the first non '-' or the
# '--' argument occurs.
def ListDashArgCallback(option, opt_str, value, parser):
   if value is None:
     value = []

   for arg in parser.rargs:
     if arg[:2].startswith('--') or arg[0] != '-':
       break
     value.append(arg)

   del parser.rargs[:len(value)]
   setattr(parser.values, option.dest, value)


# Mapping table between build mode and build configuration.
BUILD_MODES = {
  'debug': 'Debug',
  'release': 'Release',
}


# Mapping table between OS and build output location.
BUILD_ROOT = {
  'linux': os.path.join('out'),
  'freebsd': os.path.join('out'),
  'macos': os.path.join('xcodebuild'),
}

def GetBuildMode(mode):
  global BUILD_MODES
  return BUILD_MODES[mode]


def GetBuildConf(mode, arch):
  return GetBuildMode(mode) + arch.upper()


def GetBuildRoot(host_os, mode=None, arch=None):
  global BUILD_ROOT
  if mode:
    return os.path.join(BUILD_ROOT[host_os], GetBuildConf(mode, arch))
  else:
    return BUILD_ROOT[host_os]


def RunCommand(command, input=None, pollFn=None, outStream=None, errStream=None,
               killOnEarlyReturn=True, verbose=False, debug=False,
               printErrorInfo=False):
  """
  Run a command, with optional input and polling function.

  Args:
    command: list of the command and its arguments.
    input: optional string of input to feed to the command, it should be
        short enough to fit in an i/o pipe buffer.
    pollFn: if present will be called occasionally to check if the command
        should be finished early. If pollFn() returns true then the command
        will finish early.
    outStream: if present, the stdout output of the command will be written to
        outStream.
    errStream: if present, the stderr output of the command will be written to
        errStream.
    killOnEarlyReturn: if true and pollFn returns true, then the subprocess will
        be killed, otherwise the subprocess will be detached.
    verbose: if true, the command is echoed to stderr.
    debug: if true, prints debugging information to stderr.
    printErrorInfo: if true, prints error information when the subprocess
    returns a non-zero exit code.
  Returns: the output of the subprocess.

  Exceptions:
    Raises Error if the subprocess returns an error code.
    Raises ValueError if called with invalid arguments.
  """
  if verbose:
    sys.stderr.write("command %s\n" % command)
  stdin = None
  if input:
    stdin = subprocess.PIPE
  try:
    process = subprocess.Popen(args=command,
                               stdin=stdin,
                               bufsize=1,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
  except OSError as e:
    if not isinstance(command, basestring):
      command = ' '.join(command)
    if printErrorInfo:
      sys.stderr.write("Command failed: '%s'\n" % command)
    raise Error(e)

  def StartThread(out):
    queue = Queue.Queue()
    def EnqueueOutput(out, queue):
      for line in iter(out.readline, b''):
        queue.put(line)
      out.close()
    thread = threading.Thread(target=EnqueueOutput, args=(out, queue))
    thread.daemon = True
    thread.start()
    return queue
  outQueue = StartThread(process.stdout)
  errQueue = StartThread(process.stderr)

  def ReadQueue(queue, out, out2):
    try:
      while True:
        line = queue.get(False)
        out.write(line)
        if out2 != None:
          out2.write(line)
    except Queue.Empty:
      pass

  outBuf = StringIO.StringIO()
  errorBuf = StringIO.StringIO()
  if input:
    process.stdin.write(input)
  while True:
    returncode = process.poll()
    if returncode != None:
      break
    ReadQueue(errQueue, errorBuf, errStream)
    ReadQueue(outQueue, outBuf, outStream)
    if pollFn != None and pollFn():
      returncode = 0
      if killOnEarlyReturn:
        process.kill()
      break
    time.sleep(0.1)
  # Drain queue
  ReadQueue(errQueue, errorBuf, errStream)
  ReadQueue(outQueue, outBuf, outStream)

  out = outBuf.getvalue();
  error = errorBuf.getvalue();
  if returncode:
    if not isinstance(command, basestring):
      command = ' '.join(command)
    if printErrorInfo:
      sys.stderr.write("Command failed: '%s'\n" % command)
      sys.stderr.write("        stdout: '%s'\n" % out)
      sys.stderr.write("        stderr: '%s'\n" % error)
      sys.stderr.write("    returncode: %d\n" % returncode)
    raise Error("Command failed: %s" % command)
  if debug:
    sys.stderr.write("output: %s\n" % out)
  return out


def Main(argv):
  print "GuessOS() -> ", GuessOS()
  print "GuessArchitecture() -> ", GuessArchitecture()
  print "GuessCpus() -> ", GuessCpus()
  print "IsWindows() -> ", IsWindows()


class Error(Exception):
  pass


if __name__ == "__main__":
  import sys
  Main(sys.argv)
